//
// validation.cpp
// libprotea
// Tools Library for the Protea Project / Data validation for Fields
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include "../Buffer/Buffer.h"
#include "../AppHandler/AppInterface.h"
#include "../OSDep/utf8.h"
#include "fields.h"
#include "field_defns.h"
#include <ctype.h>
#include "../defns.h"
using namespace ProteaApp1;

const char PLUS = '+';
const char MINUS = '-';
const char DECIMAL_POINT_CHAR = '.';
const char ALT_DECIMAL_POINT = ',';
const char BLANK_CHAR = ' ';
const char NULL_CHAR = 0;

const char FIRST_PRINTABLE = ' ';
const char LAST_PRINTABLE = '~';


const int FLAG_NO_SPACES = 1;
const int FLAG_ALL_UPPER = 2;
const int FLAG_ALL_LOWER = 4;
const int FLAG_SENTENCE = 8;
const int FLAG_TITLE = 16;
const int FLAG_NOT_ZERO = 32;
const int FLAG_POSITIVE = 64;
const int FLAG_NEGATIVE = 128;
const int FLAG_FORCE_DECIMAL = 256;
const int FLAG_ALLOW_NULL = 512;



// Helper function
bool NUMERIC( char test ) {
	return (test >= '0' && test <= '9');
};



//
// Valid_String
//
//	Returns true if the data contains no non-printable characters
//
bool XCField::Valid_String() const {
	INFUNC( XCField::Valid_String, owner->APPDIC );

	char* temp = buffer_offset;
	char* end = temp + size;

	// UTF8 Parser internal structure
	XCUNIPARSE parse;
	memset( &parse, 0, sizeof( XCUNIPARSE ) );

	while (temp < end) {
		if (!UTF8ToUnicode( *temp, parse ))
			RETURNFUNC( false );

		if (parse.unichars == 0) {
			if (!parse.univalue)		// Handle null terminated strings
				RETURNFUNC( true )
			else if (parse.univalue < FIRST_PRINTABLE)
				RETURNFUNC( false );
		};

		temp++;
	};

	OUTFUNCRET( true );
};





//
// Valid_Alpha_Str
//
//	Returns true if the data contains only upper/lowercase
//	letters and blanks
//
bool XCField::Valid_Alpha_Str() const {
	INFUNC( XCField::Valid_Alpha_Str, owner->APPDIC );

	char* temp = buffer_offset;
	char* end = temp + size;

	// UTF8 Parser internal structure
	XCUNIPARSE parse;
	memset( &parse, 0, sizeof( XCUNIPARSE ) );

	while (temp < end) {
		if (!UTF8ToUnicode( *temp, parse ))
			RETURNFUNC( false );

		if (parse.unichars == 0) {
			if (!parse.univalue)		// Handle null terminated strings
				RETURNFUNC( true )
			else if (!isalnum( parse.univalue ) && parse.univalue != 32) // Accept characters, numbers and spaces
				RETURNFUNC( false );
		};

		temp++;
	};

	OUTFUNCRET( true );
};



//
// Valid_BLOB
//
// Returns true if field points to an allocated BLOB
//
bool XCField::Valid_BLOB() const {
	INFUNC( XCField::Valid_BLOB, owner->APPDIC );
	OUTFUNCRET( validblob );
}



//
// Chk_Display
//
//	This procedure validates a display numeric field.
//
//	Returns true if the field is valid and false if the field is invalid
//
//	A valid display numeric field may contains:
//
//		OPTIONAL LEADING BLANKS
//		OPTIONAL LEADING OR TRAILING SIGN
//		NUMERIC CHARACTERS
//		OPTIONAL DECIMAL POINT
//		OPTIONAL TRAILING BLANKS
//
//	Note:	The input field will have alternate_decimal_points
//			changed to the decimal_point for the "language".
//
bool XCField::Chk_Display() const {
	INFUNC( XCField::Chk_Display, owner->APPDIC );

	int		i;				// USED AS INDEX IN WHILE STATEMENT
	int		type=0;			// USED AS CASE NUMBER
	long	limit;			// HOLDS LENGTH

	bool	
		VALID_CHAR = true,        // true = PRESENT CHAR IS VALID
		DIGIT_VALID = true,       // true = DIGIT OK FOR THIS CHAR
		SIGN_VALID  = true,       // true = SIGN OK FOR THIS CHAR
		PERIOD_VALID = true,      // true = PERIOD OK FOR THIS CHAR
		DIGIT_FOUND = false,      // true = A DIGIT HAS BEEN FOUND
		SIGN_FOUND  = false,      // true = A SIGN HAS BEEN FOUND
		PERIOD_FOUND = false;     // true = A PERIOD HAS BEEN FOUND
	int dec_found = -1;
	int last_char = -1;

	char *test = buffer_offset;

	// THIS LOOP SCANS ENTIRE FIELD

	i = 0;
	limit = size;

	while (i < limit && *test != NULL_CHAR && VALID_CHAR) {
		if (*test == BLANK_CHAR) 
			type = 0;
		else
			if (NUMERIC( *test ))
				type = 1;
			else
				if ( *test == PLUS  || *test == MINUS )
					type = 2;
				else
					if (*test == DECIMAL_POINT_CHAR  || *test == ALT_DECIMAL_POINT  )
						type = 3;
					else
						VALID_CHAR = false;

		if (VALID_CHAR)
			switch( type ) {
				case 0:	// Blank found
					if (DIGIT_FOUND || PERIOD_FOUND) {
						DIGIT_VALID = false;
						PERIOD_VALID = false;
						if (last_char == -1)
							last_char = i;
					};
					break;

				case 1: // Digit found
                     if (DIGIT_VALID)
                        DIGIT_FOUND = true;
                     else
                        VALID_CHAR = false;
				   break;

				case 2: // Sign found
					if (SIGN_VALID) {
						if (DIGIT_FOUND || PERIOD_FOUND ) {
							DIGIT_VALID = false;
							PERIOD_VALID = false;
						};
						SIGN_FOUND = true;
						SIGN_VALID = false;
					} else
						VALID_CHAR = false;
					break;

				case 3: // Period found
					if (PERIOD_VALID) {
						PERIOD_FOUND = true;
						PERIOD_VALID = false;
						*test = DECIMAL_POINT_CHAR;
						dec_found = i;
					} else
						VALID_CHAR = false;
					break;
            }

		i++;
		test++;
	};  // While loop

	// Check decimal places...
	if (dec_found != -1) {
		int found_dec_places;

		if (last_char == -1) {
			found_dec_places = (test - buffer_offset) - dec_found - 1;
		} else {
			found_dec_places = last_char - dec_found - 1;
		};

//		printf( "NUMBER OF DECIMAL PLACES FOUND: %d", found_dec_places );
		if (found_dec_places > dec_places)
			RAISEFIELDERROR_1PARAM( ERR_TOO_MANY_DIGITS, GetFieldNumber(), GetFieldName() );

	};

	OUTFUNCRET( VALID_CHAR && DIGIT_FOUND );
};






//
// Chk_Zoned
//
//	Returns true if ???
//
bool XCField::Chk_Zoned() const {
	return false;
};




//
// Chk_Duration
//
//	Returns true if valid duration type
//
bool XCField::Chk_Duration( int type ) const {
	return true;
};



//
// Chk_Time
//
//	Returns true if ???
//
bool XCField::Chk_Time( char* buffer, long size, long type ) const {
	INFUNC( XCField::Chk_Time, owner->APPDIC );

	char Hours[2];
	char Minutes[2];
	char AM_PM_Letter;
	int i;

	buffer[ size ] = 0;

	// Special case for timestamp types
	if (type == DATE_TIMESTAMP) {
		buffer += 11;
	};

//	printf( "CHECK TIME: %d  '%s'\n", type, buffer );

	const tdata_type_defnition *source_dd = &data_type_defns[ type ];

	if (source_dd->type_group & TYPE_TIME) {
		if (IsBlank())
			RETURNFUNC( true );
	} else
		RETURNFUNC( false );

	// First check ":" for the display types
	if (type == TIME_12HR_DISPLAY || type == TIME_24HR_DISPLAY || type == TIME_24HR_DB) {
		if (buffer[2] != ':')
			RETURNFUNC( false );
	} else if (type == TIME_EU_12HR_DISP || type == TIME_EU_24HR_DISP)
		// CHECK "." FOR THE EUROPEAN DISPLAY TYPES
		if (buffer[2] != '.')
			RETURNFUNC( false );
		
	// Check the hours parts
	memcpy( Hours, buffer, 2 );

	i = 0;
	while (Hours[i] == ' ' && i < 2) {
		Hours[i] = '0';
		i++;
	};

	if (Hours[0] == '0' || Hours[0] == '1') {
		if (Hours[1] < '0' || Hours[1] > '9')
			RETURNFUNC( false );
	} else if (Hours[0] != '2') {
		RETURNFUNC( false )
	} else
		if (Hours[1] < '0' || Hours[1] > '4')
			RETURNFUNC( false );


	if (type < TIME_24HR || type == TIME_EU_12HR_DISP) {
		//  FOR 12 HR CLOCK, DON'T ALLOW HH CANNOT BE 00 OR > 12
		if (Hours[0] == '0' && Hours[1] == '0')
			RETURNFUNC( false );
		if (Hours[0] == '2')
			RETURNFUNC( false );
		if (Hours[0] == '1' && Hours[1] > '2')
			RETURNFUNC( false );
	};



	// CHECK THE MINUTES PART
	if (type == TIME_12HR || type == TIME_24HR)
		memcpy( Minutes, &buffer[2], 2 );
	else
		memcpy( Minutes, &buffer[3], 2 );

	if (Minutes[0] < '0' || Minutes[0] > '5')
		RETURNFUNC( false );
	if (Minutes[1] < '0' || Minutes[1] > '9')
		RETURNFUNC( false );

	// Change 24:00 to 00:00 to meet SQL standards, and fix julian date problems
	if (Hours[0] == '2' && Hours[1] == '4') {
		if (Minutes[0] == '0' && Minutes[1] == '0') {
			memcpy( buffer, "00", 2 );
			if (type == TIME_24HR)
				memcpy( &buffer[2], "00", 2 );
			else
				memcpy( &buffer[3], "00", 2 );
		} else {
			// It's an invalid date
			RETURNFUNC( false );
		};
	};

	if (type < TIME_24HR || type == TIME_EU_12HR_DISP) {
		// Check the A, P, N, OR M for AM/PM/NOON/MIDNIGHT
		if (type == TIME_12HR)
			AM_PM_Letter = buffer[4];
		else
			AM_PM_Letter = buffer[5];

		if (Hours[0] == '1' && Hours[1] == '2' &&
			Minutes[0] == '0' && Minutes[1] == '0') {
			if (AM_PM_Letter != 'M' && AM_PM_Letter != 'N')
				RETURNFUNC( false );
		} else
			if (AM_PM_Letter != 'A' && AM_PM_Letter != 'P')
				RETURNFUNC( false );
	};

	OUTFUNCRET( true );
};




//
// Chk_Date
//
//	Returns true if ???
//
bool XCField::Chk_Date( char* buffer ) const {
	return false;
};



/*
bool check_duration( char* SOURCE_BUFFER, int SOURCE_LENGTH, int SOURCE_TYPE);
bool chk_packed( char* FIELD, int LENGTH );
integer valid_number( char* STRING, int BFR_LEN, int LENGTH, int DECIMALS);
*/









//
// Validate
//
//	Validate data in the field
//
void XCField::Validate() {
	INFUNC( XCField::Validate, owner->APPDIC )

	owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_HIGH, "Validate Field: %s - %d", GetFieldName(), flags );

	if (flags) {
		if (type == BIG_STRING || type == STRING || type == ALPHA_STRING || type == DISPLAY_NUM) {
/* THIS FUNCTIONALITY IS CAUSING PROBLEMS WITH DSNLESS CONNECTIONS FOR SOME REASON
			if (flags & FLAG_NO_SPACES == FLAG_NO_SPACES) {
				char* temp = buffer_offset;

				// Drop whitespace at start
				int count = GetLength();
				int start = 0;
				int end = count - 1;
				while (start < count && *temp == ' ') {
					temp++;
					start++;
				};

				int counter = 0;
				for (counter=0; counter < count-start; counter++)
					temp[ counter ] = temp[ start + counter ];


				// Drop whitespace at the end
				while (end && temp[ end ] == ' ') {
					temp[ end ] = 0;
					end--;
				};


				// Now check for spaces in the middle
				if (memchr( buffer_offset, 32, end ))
					RAISEFIELDERROR_1PARAM( ERR_INV_CHAR, GetFieldNumber(), this );
			};
*/
			if (flags & FLAG_ALL_UPPER) {
				char* temp = buffer_offset;
				for (int i=0; i<GetLength(), *temp != 0; i++) {
					if (*temp >= 'a' && *temp <= 'z')
						*temp = *temp - ('a' - 'A');
					temp++;
				};
			};

			if (flags & FLAG_ALL_LOWER) {
				char* temp = buffer_offset;
				for (int i=0; i<GetLength(), *temp != 0; i++) {
					if (*temp >= 'A' && *temp <= 'Z')
						*temp = *temp - ('A' - 'a');
					temp++;
				};
			};

			if (flags & FLAG_SENTENCE) {
			};

			if (flags & FLAG_TITLE) {
			};
		};

		if (type == S_INTEGER || type == D_INTEGER) {
			long value;

			if (type == S_INTEGER) {
				value = 0;
				value = GetShort();
			} else
				value = GetLong();

			if (flags & FLAG_NOT_ZERO) {
				owner->APPDIC->ToolsTrace( TRC_FIELD, TLV_HIGH, "Check non zero flag: %ld", value );
				if (value == 0)
					throw;
			};

			if (flags & FLAG_POSITIVE) {
				if (value < 0)
					throw;
			};

			if (flags & FLAG_NEGATIVE) {
				if (value > 0)
					throw;
			};
		};

		if (flags & FLAG_FORCE_DECIMAL) {
		};
	};

	OUTFUNC()
};
